﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GeneticAlgorythm.Problem;
using GeneticAlgorythm.Algorythm;
using System.Diagnostics;

using Cudafy;
using Cudafy.Host;
using Cudafy.Translator;

namespace GeneticAlgorythm.Problem
{
    public class ObjectiveFunctionCalculatorMyGPU : ObjectiveFunctionCalculator
    {
        private GPGPU _gpu;
        private bool _gpuInitialized = false;
        float[,] _cargoQuantityMatrixOnGpu;
        float[,] _costsMatrixOnGpu;
        float[] _costsMatrixOnGpuCasted;
        float[] _cargoQuantityMatrixOnGpuCasted;
        private long[] _timerValues = new long[4];
        private Stopwatch _timer = new Stopwatch();

        public ObjectiveFunctionCalculatorMyGPU()
        {
            CudafyModule km = CudafyTranslator.Cudafy();

            _gpu = CudafyHost.GetDevice(CudafyModes.Target, CudafyModes.DeviceId);
            _gpu.LoadModule(km);
        }
        ~ObjectiveFunctionCalculatorMyGPU()
        {
            if(_gpuInitialized)
            {
                _gpu.Free(_cargoQuantityMatrixOnGpu);
                _gpu.Free(_costsMatrixOnGpu);
            }
        }

        public float calculate(Chromosome chromosome, float[,] cargoQuantityMatrix, float[,] costsMatrix)
        {
            throw new NotImplementedException();
        }

        public float[] calculate(List<Chromosome> chromosomes, float[,] cargoQuantityMatrix, float[,] costsMatrix)
        {
            int chromosomeSize = chromosomes[0].getSize();
            int numberOfChromosomes = chromosomes.Count;

            if(!_gpuInitialized)
            {
                _cargoQuantityMatrixOnGpu = _gpu.Allocate<float>(cargoQuantityMatrix);
                _costsMatrixOnGpu = _gpu.Allocate<float>(costsMatrix);
                _gpu.CopyToDevice(cargoQuantityMatrix, _cargoQuantityMatrixOnGpu);
                _gpu.CopyToDevice(costsMatrix, _costsMatrixOnGpu);
                _costsMatrixOnGpuCasted = _gpu.Cast(_costsMatrixOnGpu, chromosomeSize * chromosomeSize);
                _cargoQuantityMatrixOnGpuCasted = _gpu.Cast(_cargoQuantityMatrixOnGpu, chromosomeSize * chromosomeSize);
                _gpuInitialized = true;
            }
            _timer.Restart();


            float[] result = new float[numberOfChromosomes];
            for (int i = 0; i < numberOfChromosomes; i++)
            {
                result[i] = 0;
            }
            int[] l_chromosomes = new int[numberOfChromosomes * chromosomeSize];

            for(int i = 0 ; i < numberOfChromosomes ; i++)
            {
                for(int j = 0 ; j < chromosomeSize ; j++)
                {
                    l_chromosomes[i * chromosomeSize + j] = (int)chromosomes[i].getGene(j);
                }
            }

            float[] resultOnGpu = _gpu.Allocate<float>(result);
            int[] l_chromosomesOnGpu = _gpu.Allocate<int>(l_chromosomes);

            _gpu.CopyToDevice(l_chromosomes, l_chromosomesOnGpu);

            _timer.Stop();
            _timerValues[0] = _timer.ElapsedMilliseconds;
            _timer.Restart();
            _gpu.Launch((numberOfChromosomes/ 1024) + 1, 1024 , "calculateForChromosome", numberOfChromosomes, 
                                                                               chromosomeSize,
                                                                               resultOnGpu,
                                                                               _cargoQuantityMatrixOnGpuCasted,
                                                                               _costsMatrixOnGpuCasted,
                                                                               l_chromosomesOnGpu);

            _timer.Stop();
            _timerValues[1] = _timer.ElapsedMilliseconds;
            _timer.Restart();
            _gpu.CopyFromDevice(resultOnGpu, result);
            _timer.Stop();
            _timerValues[2] = _timer.ElapsedMilliseconds;
            _timer.Restart();

            _gpu.Free(resultOnGpu);
            _gpu.Free(l_chromosomesOnGpu);

            _timer.Stop();
            _timerValues[3] = _timer.ElapsedMilliseconds;


            return result;

        }
        public long[] getTimerValues()
        {
            return _timerValues;
        }

        [Cudafy]
        public static void calculateForChromosome(GThread thread, 
                                                  int numberOfChromosomes, 
                                                  int chromosomeSize, 
                                                  float[] result, 
                                                  float[] cargoQuantityMatrix, 
                                                  float[] costsMatrix,
                                                  int[] chromosomes)
        {


            for (int i = thread.blockIdx.x * thread.blockDim.x + thread.threadIdx.x; 
                 i < numberOfChromosomes; 
                 i += thread.blockDim.x * thread.gridDim.x)
            {
            //int i = thread.blockIdx.x;
                float tempResult = 0;
                for (int j = 0; j < chromosomeSize; ++j)
                {
                    for (int k = 0; k < chromosomeSize; ++k)
                    {
                        if (j == k) continue;
                        tempResult += cargoQuantityMatrix[j * chromosomeSize + k] * costsMatrix[(chromosomes[i * chromosomeSize + j] - 1) * chromosomeSize + (chromosomes[i * chromosomeSize + k] - 1)];
                        //result[j*chromosomeSize + k] = cargoQuantityMatrix[j * chromosomeSize + k] * costsMatrix[(chromosomes[i * chromosomeSize + j] - 1) * chromosomeSize + (chromosomes[i * chromosomeSize + k] - 1)];
                    }
                }
                result[i] = tempResult;
            }
        }
        
    }
}
